/*
 * ProGuard -- shrinking, optimization, obfuscation, and preverification of Java bytecode.
 *
 * Copyright (c) 2002-2018 GuardSquare NV
 *
 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with this program; if not, write to the Free
 * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
package proguard;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.PrintStream;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

/**
 * This class checks and prints out information about the GPL.
 *
 * @author Eric Lafortune
 */
public class GPL {
    /**
     * Prints out a note about the GPL if ProGuard is linked against unknown code.
     */
    public static void check() {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        new Exception().printStackTrace(new PrintStream(out));
        LineNumberReader reader =
            new LineNumberReader(new InputStreamReader(new ByteArrayInputStream(out.toByteArray())));

        Set unknownPackageNames = unknownPackageNames(reader);

        if (unknownPackageNames.size() > 0) {
            String uniquePackageNames = uniquePackageNames(unknownPackageNames);

            System.out.println("ProGuard is released under the GNU General Public License. You therefore");
            System.out.println("must ensure that programs that link to it (" + uniquePackageNames + "...)");
            System.out.println("carry the GNU General Public License as well. Alternatively, you can");
            System.out.println("apply for an exception with the author of ProGuard.");
        }
    }

    /**
     * Returns a set of package names from the given stack trace.
     */
    private static Set unknownPackageNames(LineNumberReader reader) {
        Set packageNames = new HashSet();

        try {
            while (true) {
                String line = reader.readLine();
                if (line == null) {
                    break;
                }

                line = line.trim();
                if (line.startsWith("at ")) {
                    line = line.substring(2).trim();
                    line = trimSuffix(line, '(');
                    line = trimSuffix(line, '.');
                    line = trimSuffix(line, '.');

                    if (line.length() > 0 && !isKnown(line)) {
                        packageNames.add(line);
                    }
                }
            }
        } catch (IOException ex) {
            // We'll just stop looking for more names.
        }

        return packageNames;
    }

    /**
     * Returns a comma-separated list of package names from the set, excluding any subpackages of packages in the set.
     */
    private static String uniquePackageNames(Set packageNames) {
        StringBuffer buffer = new StringBuffer();

        Iterator iterator = packageNames.iterator();
        while (iterator.hasNext()) {
            String packageName = (String)iterator.next();
            if (!containsPrefix(packageNames, packageName)) {
                buffer.append(packageName).append(", ");
            }
        }

        return buffer.toString();
    }

    /**
     * Returns a given string without the suffix, as defined by the given separator.
     */
    private static String trimSuffix(String string, char separator) {
        int index = string.lastIndexOf(separator);
        return index < 0 ? "" : string.substring(0, index);
    }

    /**
     * Returns whether the given set contains a prefix of the given name.
     */
    private static boolean containsPrefix(Set set, String name) {
        int index = 0;

        while (!set.contains(name.substring(0, index))) {
            index = name.indexOf('.', index + 1);
            if (index < 0) {
                return false;
            }
        }

        return true;
    }

    /**
     * Returns whether the given package name has been granted an exception against the GPL linking clause, by the
     * copyright holder of ProGuard. This method is not legally binding, but of course the actual license is. Please
     * contact the copyright holder if you would like an exception for your code as well.
     */
    private static boolean isKnown(String packageName) {
        return packageName.startsWith("java") || packageName.startsWith("sun.reflect")
            || packageName.startsWith("proguard") || packageName.startsWith("org.apache.tools.ant")
            || packageName.startsWith("org.apache.tools.maven") || packageName.startsWith("org.gradle")
            || packageName.startsWith("org.codehaus.groovy") || packageName.startsWith("org.eclipse")
            || packageName.startsWith("org.netbeans") || packageName.startsWith("com.android")
            || packageName.startsWith("com.sun.kvem") || packageName.startsWith("net.certiv.proguarddt")
            || packageName.startsWith("groovy") || packageName.startsWith("scala") || packageName.startsWith("sbt")
            || packageName.startsWith("xsbt") || packageName.startsWith("eclipseme");
    }

    public static void main(String[] args) {
        LineNumberReader reader = new LineNumberReader(new InputStreamReader(System.in));

        Set unknownPackageNames = unknownPackageNames(reader);

        if (unknownPackageNames.size() > 0) {
            String uniquePackageNames = uniquePackageNames(unknownPackageNames);

            System.out.println(uniquePackageNames);
        }
    }
}
